/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.chinasoft_ohos.commontools.ohosext.os;

import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 异步任务，简化异步执行和主线程回调方式
 *
 * @param <Params> 输入参数泛型
 * @param <Result> 输出结果泛型
 * @since 2021-03-27
 */
public abstract class SimpleAsyncTask<Params, Result> {
    private static final HiLogLabel LOG_LABEL = new HiLogLabel(HiLog.LOG_APP, 0x1, "SimpleAsyncTask");

    private static final ThreadFactory THREADFACTORY = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "SimpleAsyncTask #" + mCount.getAndIncrement());
        }
    };

    private static final Executor THREAD_POOL_EXECUTOR = Executors.newSingleThreadExecutor(THREADFACTORY);

    private static final int MESSAGE_POST_RESULT = 0x1;

    private static InternalHandler sEventHandler;
    private boolean isExecuteCalled = false;

    private final WorkerRunnable<Params, Result> mWorker;
    private final FutureTask<Result> mFuture;

    private final AtomicBoolean mTaskInvoked = new AtomicBoolean();

    private final EventHandler mEventHandler;

    SimpleAsyncTask() {
        mEventHandler = getMainHandler();
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() {
                mTaskInvoked.set(true);
                Result result = null;
                result = doInBackground(mParams);
                postResult(result);
                return result;
            }
        };
        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    HiLog.error(LOG_LABEL, e.getMessage());
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()", e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

    private static EventHandler getMainHandler() {
        synchronized (SimpleAsyncTask.class) {
            if (sEventHandler == null) {
                sEventHandler = new InternalHandler(EventRunner.getMainEventRunner());
            }
            return sEventHandler;
        }
    }

    private void postResultIfNotInvoked(Result result) {
        final boolean isTaskInvoked = mTaskInvoked.get();
        if (!isTaskInvoked) {
            postResult(result);
        }
    }

    private Result postResult(Result result) {
        InnerEvent event = InnerEvent.get(MESSAGE_POST_RESULT, new TaskResult<>(this, result));
        mEventHandler.sendEvent(event);
        return result;
    }

    /**
     * 后台执行任务
     *
     * @param params 执行任务参数
     * @return 执行任务结果
     */
    protected abstract Result doInBackground(Params... params);

    /**
     * 后台执行任务前的准备回调
     */
    protected void onPreExecute() {
    }

    /**
     * 后台执行任务完成后的回调
     *
     * @param result 后台任务执行的结果
     */
    protected void onPostExecute(Result result) {
    }

    /**
     * 后台任务取消
     *
     * @param result 后台任务取消时返回的结果
     */
    protected void onCancelled(Result result) {
    }

    /**
     * 执行后台任务
     *
     * @param params 后台任务参数
     * @return 返回一个后台任务实例
     */
    public final SimpleAsyncTask<Params, Result> execute(Params... params) {
        if (isExecuteCalled) {
            HiLog.error(LOG_LABEL, "The method SimpleAsyncTask#execute can not be call more than one time!");
            return this;
        }
        isExecuteCalled = true;
        onPreExecute();
        mWorker.mParams = params;
        THREAD_POOL_EXECUTOR.execute(mFuture);
        return this;
    }

    private void postResultOnMainThread(Result result) {
        onPostExecute(result);
    }

    /**
     * 内部Handler
     *
     * @since 2021-03-27
     */
    private static class InternalHandler extends EventHandler {
        InternalHandler(EventRunner eventRunner) {
            super(eventRunner);
        }

        @Override
        protected void processEvent(InnerEvent msg) {
            TaskResult<?> result = (TaskResult<?>) msg.object;
            if (msg.eventId == MESSAGE_POST_RESULT) { // There is only one result
                result.mTask.postResultOnMainThread(result.mData);
            }
        }
    }

    /**
     * 内部任务
     *
     * @param <Params> 入参
     * @param <Result> 返回结果参数
     * @since 2021-03-27
     */
    private abstract static class WorkerRunnable<Params, Result> implements Callable<Result> {
        Params[] mParams;
    }

    /**
     * 任务结果
     *
     * @param <Data> 结果参数类型
     * @since 2021-03-27
     */
    private static class TaskResult<Data> {
        final SimpleAsyncTask mTask;
        final Data mData;

        TaskResult(SimpleAsyncTask task, Data data) {
            mTask = task;
            mData = data;
        }
    }
}
